package com.barcode.utils;

import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;

import javax.imageio.ImageIO;

import org.springframework.util.StringUtils;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.oned.Code128Writer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * 类描述: 二维码生成工具类
 * 
 * @author jianweiwang
 * @version v1.0
 * @link wjw@choicesoft.com.cn
 * @date 2019/4/1 11:00
 **/
public class BarcodeUtils {
    private static final String CHARSET = "utf-8";
    private static final String FORMAT = "JPG";
    /** 条形码宽度 */
    private static final int WIDTH = 300;
    /** 条形码高度 */
    private static final int HEIGHT = 50;
    /** 加文字 条形码 */
    private static final int WORD_HEIGHT = 75;

    /** 二维码尺寸 */
    private static final int QR_CODE_SIZE = 300;
    /** LOGO宽度 */
    private static final int LOGO_WIDTH = 60;
    /** LOGO高度 */
    private static final int LOGO_HEIGHT = 60;

    /**
     * 设置 条形码参数
     */
    private static Map<EncodeHintType, Object> hints = new HashMap<>(5);
    static {
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
    }

    /**
     * 生成 图片缓冲
     * 
     * @author fxbin
     * @param vaNumber
     *            VA 码
     * @return 返回BufferedImage
     */
    private static BufferedImage getBarCode(String vaNumber) {
        try {
            Code128Writer writer = new Code128Writer();
            // 编码内容, 编码类型, 宽度, 高度, 设置参数
            BitMatrix bitMatrix = writer.encode(vaNumber, BarcodeFormat.CODE_128, WIDTH, HEIGHT, hints);
            return MatrixToImageWriter.toBufferedImage(bitMatrix);
        } catch (WriterException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 把带logo的二维码下面加上文字
     * 
     * @author fxbin
     * @param image
     *            条形码图片
     * @param words
     *            文字
     * @return 返回BufferedImage
     */
    private static BufferedImage insertWords(BufferedImage image, String words) {
        // 新的图片，把带logo的二维码下面加上文字
        if (!StringUtils.isEmpty(words)) {

            BufferedImage outImage = new BufferedImage(WIDTH, WORD_HEIGHT, BufferedImage.TYPE_INT_RGB);

            Graphics2D g2d = outImage.createGraphics();

            // 抗锯齿
            setGraphics2D(g2d);
            // 设置白色
            setColorWhite(g2d);

            // 画条形码到新的面板
            g2d.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
            // 画文字到新的面板
            Color color = new Color(20, 60, 60);
            g2d.setColor(color);
            // 字体、字型、字号
            g2d.setFont(new Font("微软雅黑", Font.PLAIN, 18));
            // 文字长度
            int strWidth = g2d.getFontMetrics().stringWidth(words);
            // 总长度减去文字长度的一半 （居中显示）
            int wordStartX = (WIDTH - strWidth) / 2;
            // height + (outImage.getHeight() - height) / 2 + 12
            int wordStartY = HEIGHT + 20;

            // 画文字
            g2d.drawString(words, wordStartX, wordStartY);
            g2d.dispose();
            outImage.flush();
            return outImage;
        }
        return null;
    }

    /**
     * 设置 Graphics2D 属性 （抗锯齿）
     * 
     * @param g2d
     *            Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制
     */
    private static void setGraphics2D(Graphics2D g2d) {
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
        Stroke s = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
        g2d.setStroke(s);
    }

    /**
     * 设置背景为白色
     * 
     * @param g2d
     *            Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制
     */
    private static void setColorWhite(Graphics2D g2d) {
        g2d.setColor(Color.WHITE);
        // 填充整个屏幕
        g2d.fillRect(0, 0, 600, 600);
        // 设置笔刷
        g2d.setColor(Color.BLACK);
    }

    private static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE,
                hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        if (logoPath == null || "".equals(logoPath)) {
            return image;
        }
        // 插入图片
        BarcodeUtils.insertImage(image, logoPath, needCompress);
        return image;
    }

    /**
     * 插入LOGO
     *
     * @param source
     *            二维码图片
     * @param logoPath
     *            LOGO图片地址
     * @param needCompress
     *            是否压缩
     * @throws Exception
     */
    private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws Exception {
        File file = new File(logoPath);
        if (!file.exists()) {
            throw new Exception("logo file not found.");
        }
        Image src = ImageIO.read(new File(logoPath));
        int width = src.getWidth(null);
        int height = src.getHeight(null);
        // 是否压缩LOGO
        if (needCompress) {
            if (width > LOGO_WIDTH) {
                width = LOGO_WIDTH;
            }
            if (height > LOGO_HEIGHT) {
                height = LOGO_HEIGHT;
            }
            Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            // 绘制缩小后的图
            g.drawImage(image, 0, 0, null);
            g.dispose();
            src = image;
        }
        // 插入LOGO
        Graphics2D graph = source.createGraphics();
        int x = (QR_CODE_SIZE - width) / 2;
        int y = (QR_CODE_SIZE - height) / 2;
        graph.drawImage(src, x, y, width, height, null);
        Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }

    /**
     * 生成二维码(内嵌LOGO) 二维码文件名随机，文件名可能会有重复
     *
     * @param content
     *            内容
     * @param logoPath
     *            LOGO地址
     * @param destPath
     *            存放目录
     * @param needCompress
     *            是否压缩LOGO
     */
    private static String encode(String content, String logoPath, String destPath, boolean needCompress)
            throws Exception {
        BufferedImage image = BarcodeUtils.createImage(content, logoPath, needCompress);
        mkdir(destPath);
        String fileName = new Random().nextInt(99999999) + "." + FORMAT.toLowerCase();
        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
        return fileName;
    }

    /**
     * 生成二维码(内嵌LOGO) 调用者指定二维码文件名
     *
     * @param content
     *            内容
     * @param logoPath
     *            LOGO地址
     * @param destPath
     *            存放目录
     * @param fileName
     *            二维码文件名
     * @param needCompress
     *            是否压缩LOGO
     * @throws Exception
     */
    private static String encode(String content, String logoPath, String destPath, String fileName,
            boolean needCompress) throws Exception {
        BufferedImage image = BarcodeUtils.createImage(content, logoPath, needCompress);
        mkdir(destPath);
        fileName = fileName.substring(0, fileName.indexOf(".") > 0 ? fileName.indexOf(".") : fileName.length()) + "."
                + FORMAT.toLowerCase();
        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
        return fileName;
    }

    /**
     * 生成二维码(内嵌LOGO)
     *
     * @param content
     *            内容
     * @param logoPath
     *            LOGO地址
     * @param destPath
     *            存储地址
     */
    public static String encode(String content, String logoPath, String destPath) throws Exception {
        return BarcodeUtils.encode(content, logoPath, destPath, false);
    }

    /**
     * 生成二维码
     *
     * @param content
     *            内容
     * @param destPath
     *            存储地址
     * @throws Exception
     */
    public static String encode(String content, String destPath) throws Exception {
        return BarcodeUtils.encode(content, null, destPath, false);
    }

    /**
     * 生成二维码(内嵌LOGO)
     *
     * @param content
     *            内容
     * @param logoPath
     *            LOGO地址
     * @param output
     *            输出流
     * @param needCompress
     *            是否压缩LOGO
     */
    private static void encode(String content, String logoPath, OutputStream output, boolean needCompress)
            throws Exception {
        BufferedImage image = BarcodeUtils.createImage(content, logoPath, needCompress);
        ImageIO.write(image, FORMAT, output);
    }

    /**
     * 生成二维码
     *
     * @param content
     *            内容
     * @param output
     *            输出流
     */
    public static void encode(String content, OutputStream output) throws Exception {
        BarcodeUtils.encode(content, null, output, false);
    }

    /**
     * 当文件夹不存在时，mkdirs会自动创建多层目录，区别于mkdir． (mkdir如果父目录不存在则会抛出异常)
     * 
     * @param destPath
     *            存放目录
     */
    private static void mkdir(String destPath) {
        File file = new File(destPath);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }

    /**
     * 解析二维码
     *
     * @param file
     *            二维码图片
     */
    private static String decode(File file) throws Exception {
        BufferedImage image;
        image = ImageIO.read(file);
        if (image == null) {
            return null;
        }
        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        Result result;
        Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();
        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        result = new MultiFormatReader().decode(bitmap, hints);
        return result.getText();
    }

    /**
     * 解析二维码
     *
     * @param path
     *            二维码图片地址
     */
    public static String decode(String path) throws Exception {
        return BarcodeUtils.decode(new File(path));
    }

    public static void main(String[] args) throws Exception {
        // 条形码
        BufferedImage image = insertWords(getBarCode("123456789"), "GB/2003 1.1 20190401");
        assert image != null;
        ImageIO.write(image, "jpg", new File("barcode.jpg"));
        // 二维码
        // String text = "http://localhost:8083/communication/distribution?qrid=54999";
        // 不含Logo
        // BarcodeUtils.encode(text, null, "C:\\\\Users\\\\ThinkPad\\\\Pictures", true);
        // 含Logo，不指定二维码图片名
        // BarcodeUtils.encode(text, "e:\\csdn.jpg", "e:\\", true);
        // 含Logo，指定二维码图片名
        // BarcodeUtils.encode(text, "C:\\Users\\ThinkPad\\Pictures\\mail-unread.png",
        // "C:\\Users\\ThinkPad\\Pictures", "qrcode", true);
    }
}
